package com.googlecode.pwlodars.piteststat.tests;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;

/**
 * Check if in object inside collection there are matched properties
 * 
 * For example
 * 
 * Bean
 * *name
 * *surname
 * 
 * [bean1{"aa","bb"},bean2{"cc","dd"}]
 * 
 * containsProperties("name","aa","bb")
 *
 * @author  Pawel Wlodarski (pawel.wlodarski@tomtom.com)
 *
 */
public class ContainsPropertiesInCollection extends BaseMatcher<Collection<? extends Object>> {

    private final String propertyname;

    private final Object[] anticipatedValues;

    private Object[] receivedValues;

    public ContainsPropertiesInCollection(final String propertyName, final Object... values) {
        this.propertyname = propertyName;
        anticipatedValues = values;
    }

    @SuppressWarnings("unchecked")
    public boolean matches(final Object item) {
        final Collection<? extends Object> collection = (Collection<? extends Object>) item;

        try {
            readValues(collection);

            return Arrays.equals(anticipatedValues, receivedValues);
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void readValues(final Collection<? extends Object> collection) throws NoSuchFieldException, IllegalAccessException {
        final Field field = getFieldFromCollectionObject(collection);
        field.setAccessible(true);

        final List<Object> readedValues = new LinkedList<Object>();

        for (final Object object : collection) {
            final Object value = field.get(object);
            readedValues.add(value);
        }

        receivedValues = readedValues.toArray();
    }

    private Field getFieldFromCollectionObject(final Collection<? extends Object> collection) throws NoSuchFieldException {
        final Class<? extends Object> receivedClass = collection.iterator().next().getClass();
        final Map<String, Field> fields = obtainDeclaredFields(receivedClass);
        return fields.get(propertyname);
    }

    private Map<String, Field> obtainDeclaredFields(final Class<? extends Object> contextClass) {

        if (contextClass == null) {
            return new HashMap<String, Field>();
        }

        final Map<String, Field> fields = getFieldsFromSingleClass(contextClass);

        fields.putAll(obtainDeclaredFields(contextClass.getSuperclass()));

        return fields;
    }

    private Map<String, Field> getFieldsFromSingleClass(final Class<? extends Object> contextClass) {
        final Map<String, Field> fields = new HashMap<String, Field>();
        for (final Field field : contextClass.getDeclaredFields()) {
            fields.put(field.getName(), field);
        }
        return fields;
    }

    public void describeTo(final Description description) {
        description.appendText("\nValues doesn't match, should be : \n");
        description.appendText(Arrays.toString(anticipatedValues));
        description.appendText("\ngot : \n");
        description.appendText(Arrays.toString(receivedValues));
    }

    @Factory
    public static Matcher<Collection<? extends Object>> containProperties(final String propertyName, final Object... values) {
        return new ContainsPropertiesInCollection(propertyName, values);
    }
}